#include "shadow_mapping_demo.h"

#include "geometry/matrix4x4.h"
#include "graphics/error_gl.h"
#include "graphics/utils.h"
#include "graphics/program_glsl.h"
#include "graphics/framebuffer_object.h"
#include "graphics/image_texture_gl.h"

namespace venus {
void DrawScene(float angle) {
  //Display lists for objects
  static GLuint spheresList=0, torusList=0, baseList=0;

  //Create spheres list if necessary
  if(!spheresList) {
    spheresList=glGenLists(1);
    glNewList(spheresList, GL_COMPILE); {
      glColor3f(0.0f, 1.0f, 0.0f);
      glPushMatrix();

      glTranslatef(0.45f, 1.0f, 0.45f);
      glutSolidSphere(0.2, 24, 24);

      glTranslatef(-0.9f, 0.0f, 0.0f);
      glutSolidSphere(0.2, 24, 24);

      glTranslatef(0.0f, 0.0f,-0.9f);
      glutSolidSphere(0.2, 24, 24);

      glTranslatef(0.9f, 0.0f, 0.0f);
      glutSolidSphere(0.2, 24, 24);

      glPopMatrix();
    }
    glEndList();
  }

  //Create torus if necessary
  if(!torusList) {
    torusList=glGenLists(1);
    glNewList(torusList, GL_COMPILE);
    {
      glColor3f(1.0f, 0.0f, 0.0f);
      glPushMatrix();

     /* glTranslatef(0.0f, 0.5f, 0.0f);
      glRotatef(90.0f, 1.0f, 0.0f, 0.0f);*/
      glutSolidTorus(0.2, 0.5, 24, 48);

      glPopMatrix();
    }
    glEndList();
  }

  //Create base if necessary
  if(!baseList) {
    baseList=glGenLists(1);
    glNewList(baseList, GL_COMPILE);
    {
      glColor3f(0.0f, 0.0f, 1.0f);
      glPushMatrix();

     /* glScalef(1.0f, 0.05f, 1.0f);*/
      glutSolidCube(3.0f);

      glPopMatrix();
    }
    glEndList();
  }
  //Draw objects
  glCallList(baseList);
  glCallList(torusList);
  
  glPushMatrix();
  glRotatef(angle, 0.0f, 1.0f, 0.0f);
  glCallList(spheresList);
  glPopMatrix();
}

bool ShadowMappingDemo::Initialize() {
  glewInit();
  render_program_ = new ProgramGLSL();
  render_program_->Load("shaders/draw_vertex.glsl", "shaders/draw_fragment.glsl");
  render_program_->Link();
  if (!render_program_->is_linked()) return false;
  shadow_map_var_ = render_program_->GetUniformVar("shadow_map");
  shadow_map_ = new ImageTextureGL;
  shadow_map_->Initialize(shadow_map_width_, shadow_map_height_, 
                         GL_DEPTH_COMPONENT, 
                         kUseDefaultParameters|kDepthTexture);
  fbo_ = new FramebufferObject();
  fbo_->Bind();
  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);
  fbo_->AttachTexture(shadow_map_->target(), shadow_map_->tex_id(), 
                      GL_DEPTH_ATTACHMENT_EXT);
  fbo_->IsValid();
  fbo_->Disable();

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glEnable(GL_NORMALIZE);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
  render_method_ = 0;
  select_ = 0;
  return true;
}

void ShadowMappingDemo::RenderScene() {
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45, static_cast<float>(this->width())/this->height(),
                 1, 100);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  camera_.ApplyViewTransform();
  Matrix4x4 mat;
  glGetFloatv(GL_MODELVIEW_MATRIX, mat.ptr());
  Matrix4x4 inv_camera_view_mat = Inverse(mat);

  //glEnable(GL_TEXTURE_2D);
  shadow_map_->Bind();

  //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
  //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glTranslatef(0.5f, 0.5f, 0.5f - 0.009);
  glScalef( 0.5f, 0.5f, 0.5f );
  glMultMatrixf(light_projection_matrix_);
  glMultMatrixf(light_view_matrix_);
  //glMultMatrixf(inv_camera_view_mat.ptr());

  glMatrixMode(GL_MODELVIEW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_POSITION, light_.pos.ptr());

  render_program_->Begin();
  GLint var = render_program_->GetUniformVar("select");
  glUniform1i(var, select_);
  glUniform1iARB(shadow_map_var_, 0);
  venus::DrawScene(angle);
  render_program_->End();
  //glDisable(GL_TEXTURE_2D);
  glDisable(GL_LIGHT0);
  glDisable(GL_LIGHTING);
}

void ShadowMappingDemo::OnDisplay() {
  glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
  this->UpdateShadowMap();

  //glCullFace(GL_BACK);
  glViewport(0, 0, this->width(), this->height());
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

  if (render_method_) {
    VisualizeTexture(*shadow_map_);
  } else {
    this->RenderScene();
  }
  DisplayStastics("frame rate", fps_counter_.Update(), "fps");
  glutSwapBuffers();
}

void ShadowMappingDemo::UpdateShadowMap() {
  fbo_->Bind();
  fbo_->IsValid();
  glViewport(0, 0, shadow_map_width_, shadow_map_height_);
  glClear(GL_DEPTH_BUFFER_BIT);
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  float ratio = static_cast<float>(shadow_map_width_)/shadow_map_height_;
  // the perspective region should be properly set, otherwise part of the
  // scene may not be presented in the shadow map
  gluPerspective(75, ratio, 2, 8);
  glGetFloatv(GL_PROJECTION_MATRIX, light_projection_matrix_);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  const Point &pos = light_.pos;
  const Point &at = light_.at;
  gluLookAt(pos.x, pos.y, pos.z, at.x, at.y, at.z, 0, 1, 0);
  glGetFloatv(GL_MODELVIEW_MATRIX, light_view_matrix_);

  //glCullFace(GL_FRONT);
  venus::DrawScene(angle);
  fbo_->Disable();
}

void ShadowMappingDemo::UpdateTextureMatrix() {
/*  //current mode is project

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  //glTranslatef(0.5f, 0.5f, 0.5f - 0.005f);
  //glScalef(0.5f, 0.5f, 0.5f);
  glMultMatrixf(light_projection_matrix_);
  glMultMatrixf(light_view_matrix_);
  const venus::Point &pos = camera_.position();
  const venus::Point &at = camera_.target();
  const venus::Vector3 &up = camera_.up();
  Matrix4x4 mat = Matrix4x4::LookAt(::Point(pos.x, pos.y, pos.z), 
                                    ::Point(at.x, at.y, at.z), 
                                    ::Vector(up.x, up.y, up.z)).Invert();
  glMultMatrixf(mat.GetDataPtr());
  glGetFloatv(GL_TRANSPOSE_MODELVIEW_MATRIX, camera_to_light_matrix_);
  glPopMatrix();
  */
}

void ShadowMappingDemo::OnResize(int width, int height) {
  this->set_width(width);
  this->set_height(height);

  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  camera_.set_aspect_ratio(static_cast<double>(width)/height);
  camera_.ApplyProjectionTransform(); 
  glMatrixMode(GL_MODELVIEW);
}

void ShadowMappingDemo::OnIdle() {
  angle += 0.03;
  this->ReDraw();
}

void ShadowMappingDemo::OnKeyboard(unsigned char key, int x, int y) {
  switch (key) {
    case 'a':
      camera_.Yaw(-1);
      break;
    case 'd':
      camera_.Yaw(1);
      break;
    case 'w':
      camera_.Walk(1);
      break;
    case 's':
      camera_.Walk(-1);
      break;
    case 'q':
      camera_.Pitch(1);
      break;
    case 'z':
      camera_.Pitch(-1);
      break;
    case '1':
      render_method_ = 1;
      break;
    case '0':
      render_method_ = 0;
    case 't':
      select_ = 1 - select_;
      break;
    default:
      return;
  }
}

ShadowMappingDemo::~ShadowMappingDemo() {
  Utils::Delete(shadow_map_);
  Utils::Delete(render_program_);
  Utils::Delete(fbo_);
}

} // namespace venus
